package client;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.nio.file.*;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.Semaphore;

import static java.nio.file.StandardWatchEventKinds.*;

import shared.*;
import static java.nio.file.StandardOpenOption.*;

public class NeilTest
{
    public Semaphore myfilesLock;
    public User currentUser;

    private MasterServerInterface masterServer;
    private Thread eventHandlerThread;

    public NeilTest()
    {
	this.connectToMasterServer();

	this.eventHandlerThread = new Thread(new FolderWatcherHandler());

	myfilesLock = new Semaphore(1);
    }

    public void init() throws Exception
    {
	if (Files.notExists(Configuration.getHomePath()))
	{
	    Files.createDirectories(Configuration.getHomePath());
	}
	;
	if (!Files.isDirectory(Configuration.getHomePath()))
	{
	    throw new Exception(
		    "The home sync path in configuration is not a directory");
	}
	;
	Toolkit.getInstance().traverse(Configuration.getHomePath().toString());
	this.restoreFromDisk();
    }

    public void startEventHandler()
    {
	eventHandlerThread.start();
    }

    // get remote reference to Master Server
    public void connectToMasterServer()
    {
	Registry registry = null;
	try
	{
	    registry = LocateRegistry.getRegistry(Constants.MASTER_SERVER_1_IP);
	    this.masterServer = (MasterServerInterface) registry
		    .lookup(Constants.MASTER_SERVER_NAME);
	} catch (Exception e1)
	{
	    System.out
		    .println("Failed to connect to master server 1, now trying master server 2!");
	    try
	    {
		registry = LocateRegistry
			.getRegistry(Constants.MASTER_SERVER_2_IP);
		this.masterServer = (MasterServerInterface) registry
			.lookup(Constants.MASTER_SERVER_NAME);
	    } catch (Exception e2)
	    {
		System.out
			.println("Failed to connect to master server 2, now trying master server 3!");
		try
		{
		    registry = LocateRegistry
			    .getRegistry(Constants.MASTER_SERVER_3_IP);
		    this.masterServer = (MasterServerInterface) registry
			    .lookup(Constants.MASTER_SERVER_NAME);
		} catch (Exception e3)
		{
		    System.out.println("All master servers failed! Now exit..");
		    System.exit(-1);
		}
	    }
	}
	System.out.println("Successfully connected to master server!");
    }

    class WriteChunk implements Runnable
    {
	private DataChunk chunk;

	WriteChunk(DataChunk dataChunk)
	{
	    this.chunk = dataChunk;
	}

	// TODO
	@Override
	public void run()
	{

	}

    }

    class FolderWatcherHandler implements Runnable
    {

	@Override
	public void run()
	{
	    WatchService folderWatcher = Configuration.getFolderWatcher();
	    while (true)
	    {
		WatchKey key = null;
		try
		{
		    key = folderWatcher.poll();
		} catch (Exception x)
		{
		    System.err.println("File watcher failed!");
		}

		if (key == null)
		{
		    try
		    {
			// In unit of millisecond
			Thread.sleep(3 * 1000);
		    } catch (InterruptedException e)
		    {
		    }
		} else
		{
		    for (WatchEvent<?> event : key.pollEvents())
		    {
			WatchEvent.Kind<?> kind = event.kind();
			if (event == OVERFLOW)
			    continue;

			@SuppressWarnings("unchecked")
			WatchEvent<Path> ev = (WatchEvent<Path>) event;
			Path file = ev.context();
			file = file.toAbsolutePath();
			Path homePath = Configuration.getHomePath();
			String relativePathname = homePath.relativize(file)
				.toString();

			if (kind == ENTRY_CREATE)
			{
			    // TODO implement the actions to take
			    System.out.println("Create" + relativePathname
				    + file.toString());

			} else if (kind == ENTRY_MODIFY)
			{
			    System.out.println("Modify" + relativePathname
				    + file.toString());
			} else if (kind == ENTRY_DELETE)
			{
			    System.out.println("DELETE" + relativePathname
				    + file.toString());
			}
		    }

		    boolean valid = key.reset();
		    if (!valid)
			break;
		}
	    }
	}
    }

    public void test()
    {
	User newUser = new User();
	newUser.setUsername("mohanz");
	newUser.setPassword("jiji");

	FileDescriptor ds = new FileDescriptor();
	ds.setPathname("/abcd/abc");
	ds.setDirectory(false);
	ds.setOwners(new ArrayList<String>());
	ds.getOwners().add("mohanz");

	try
	{
	    this.masterServer.signUp(newUser);
	    ds = this.masterServer.requestCreate(ds);
	    byte[] bytes = new byte[10];
	    for (int i = 0; i < bytes.length; i++)
		bytes[i] = 1;

	    ds.getDataServers().get(0).getRemoteDataServer()
		    .writeData(ds, new DataChunk(bytes, 0, true));
	    for (int i = 0; i < bytes.length; i++)
		bytes[i] = 2;
	    ds.getDataServers().get(0).getRemoteDataServer()
		    .writeData(ds, new DataChunk(bytes, 0, true));

	} catch (RemoteException e)
	{
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	}
	System.out.println(ds.getFileID());
	System.out.println(ds.getDataServers().get(0).getHostName());

	String s;

	this.restoreFromDisk();
	s = this.currentUser.myFiles.toString();
	System.out.println("What is read:");
	System.out.println(s);

	this.currentUser.myFiles.put("Test", ds);
	System.out.println("Modifying");
	this.currentUser.myFiles.get("Test").setPathname("Changed");
	this.saveToDisk();
	System.out.println("Saved");

	this.restoreFromDisk();
	s = this.currentUser.myFiles.toString();
	System.out.println("What is read:");
	System.out.println(s);

    }

    @SuppressWarnings("unchecked")
    private void restoreFromDisk()
    {
	this.currentUser = new User();
	this.currentUser.myFiles = new HashMap<String, FileDescriptor>();

	try
	{
	    FileInputStream fis = new FileInputStream(
		    Configuration.STORAGE_PATH);
	    ObjectInputStream storage = new ObjectInputStream(fis);
	    this.currentUser.myFiles = (HashMap<String, FileDescriptor>) storage
		    .readObject();

	} catch (Exception e)
	{
	    // TODO Auto-generated catch block
	    System.err.append(e.getMessage());
	}
    }

    private void saveToDisk()
    {
	ObjectOutputStream storage = null;
	Path file = Paths.get(Configuration.STORAGE_PATH);
	try
	{
	    OutputStream fos = Files.newOutputStream(file, CREATE);
	    storage = new ObjectOutputStream(fos);
	    this.myfilesLock.acquire();
	    storage.writeObject(this.currentUser.myFiles);
	    this.myfilesLock.release();
	} catch (Exception e)
	{
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	}

    }

    public void neilTest()
    {
	User newUser = new User();
	newUser.setUsername("neil");
	newUser.setPassword("jiji");

	FileDescriptor ds = new FileDescriptor();
	ds.setPathname("/abcd/abc");
	ds.setDirectory(false);
	ds.setOwners(new ArrayList<String>());
	ds.getOwners().add("neil");

	try
	{
	     this.masterServer.signUp(newUser);
	     ds = this.masterServer.requestCreate(ds);
	     byte[] bytes = new byte[10];
	     for (int i = 0; i < bytes.length; i++)
	     bytes[i] = 1;
	    
	     ds.getDataServers().get(0).getRemoteDataServer().writeData(ds,
	     new DataChunk(bytes, 0, true));
	     for (int i = 0; i < bytes.length; i++)
	     bytes[i] = 2;
	     ds.getDataServers().get(0).getRemoteDataServer().writeData(ds,
	     new DataChunk(bytes, 0, true));
	    
	     ds = this.masterServer.getLatestFileDescriptor(ds.getFileID());
	    
	     //ds.setFileID(1);
	     ds = this.masterServer.requestModify(ds);
	     System.out.println(ds.isLocked());

	    User user = new User();
	    user = this.masterServer.getUser("mohanz");
	    for (long fileID : user.getMyFileIDs())
	    {
		System.out.println(fileID);
	    }
	    this.masterServer.requestDelete(1);
	    
	    //this.masterServer.requestShare(1, "neil");
	    user = this.masterServer.getUser("neil");
	    for (ShareInvite inv : user.getMyInvites())
	    {
		System.out.println("neil invite"+inv.getFileID());
	    }
	    //this.masterServer.confirmShare(1, "neil");
	    for (long fileID : user.getMyFileIDs())
	    {
		System.out.println("Neil" + fileID);
	    }

	} catch (RemoteException e)
	{
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	}
	// System.out.println(ds.getFileID());
	//System.out.println(ds.getDataServers().get(0).getHostName());
    }

    public static void main(String[] args)
    {
	NeilTest me = new NeilTest();
	me.neilTest();

    }

}
